
const COLOR_PATTERN = new RegExp('#[0-9|A-F]{6}')

const DATE_PATTERN = new RegExp('[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}')

const COLOR_CHARACTORS = '0123456789ABCDEF'

const ONE_DAY_MILLISECOND = 24 * 60 * 60 * 1000

const UP = 1
const DOWN = 2

const mainVue = new Vue({
    el: 'main',
    data: {
        host: window.location.origin,
        title: '任务状态甘特图',
        tasks: [],
        taskEdition: {},
        ganttChart: null,
        errMsg: '',
    },
    created: function() {
    },
    mounted: function() {
        console.log('mounted')
        //load example
        let exampleTask1StartDate = new Date(this.formatDate(new Date()))
        let exampleTask1StartDateText = this.formatDate(exampleTask1StartDate)
        let exampleTask1EndDate = new Date(exampleTask1StartDate.getTime() + 4 * ONE_DAY_MILLISECOND)
        let exampleTask1EndDateText = this.formatDate(exampleTask1EndDate)
        let exampleTask2StartDate = new Date(exampleTask1EndDate.getTime() - ONE_DAY_MILLISECOND)
        let exampleTask2StartDateText = this.formatDate(exampleTask2StartDate)
        let exampleTask2EndDate = new Date(exampleTask2StartDate.getTime() + 5 * ONE_DAY_MILLISECOND)
        let exampleTask2EndDateText = this.formatDate(exampleTask2EndDate)
        this.tasks.push({
            id: 0,
            name: '任务1',
            startTime: exampleTask1StartDate.getTime(),
            startTimeText: exampleTask1StartDateText,
            endTime: exampleTask1EndDate.getTime(),
            endTimeText: exampleTask1EndDateText,
            color: '#F98563'
        })
        this.tasks.push({
            id: 1,
            name: '任务2',
            startTime: exampleTask2StartDate.getTime(),
            startTimeText: exampleTask2StartDateText,
            endTime: exampleTask2EndDate.getTime(),
            endTimeText: exampleTask2EndDateText,
            color: '#A2E068'
        })
        this.draw()
    },
    watch: {
        title: function(val) {
            if (null === val || undefined === val) {
                return
            }
            if (val === '') return
            this.title = val
            this.draw()
        },
    },
    methods: {
        validateTask: function(task) {
            if (task.id === null) {
                this.errMsg = '缺任务id'
                return false
            }
            if (task.name === undefined || task.name === null || task.name.length < 1) {
                this.errMsg = '缺任务名称'
                return false
            }
            if (task.startTimeText === undefined || task.startTimeText === null || task.startTimeText.length < 1 || !DATE_PATTERN.test(task.startTimeText)) {
                this.errMsg = '不合法开启时间: ' + task.startTimeText
                return false
            }
            if (task.endTimeText === undefined || task.endTimeText === null || task.endTimeText.length < 1 || !DATE_PATTERN.test(task.endTimeText)) {
                this.errMsg = '不合法结束时间' + task.endTimeText
                return false
            }
            if (task.color === undefined || task.color === null || task.color.length != 7 || !this.isColor(task.color)) {
                task.color = this.randomColor()
            }
            task.startTime = new Date(task.startTimeText).getTime()
            task.endTime = new Date(task.endTimeText).getTime()
            if (task.endTime < task.startTime) {
                this.errMsg = '结束时间' + task.endTimeText + '早于开始时间' + task.startTimeText
                return false
            }
            this.errMsg = ''
            return true
        },
        sortTasks: function() {
            this.tasks.sort((a, b) => {
                if (a.id < b.id) return -1
                else if (a.id > b.id) return 1
                else return 0
            })
        },
        openTaskCreateModal: function () {
            let maxId
            if (undefined === this.tasks || null === this.tasks || this.tasks.length < 1) maxId = -1
            else maxId = this.tasks[this.tasks.length - 1].id
            this.taskEdition = {
                id: maxId + 1
            }
            $().dialog({
                "target":"#task-dialog",
                "size":"dialog-middle",
                "style":"bg-white radius-big shadow",
                "dismiss":false,
            })
        },
        saveTask: function () {
            if (!this.validateTask(this.taskEdition)) {
                alert(this.errMsg)
                return
            }
            let maxId = 0
            if (undefined !== this.tasks && null !== this.tasks && this.tasks.length > 0) {
                maxId = this.tasks[this.tasks.length - 1].id
            }
            if (this.taskEdition.id <= maxId) {
                this.tasks[this.taskEdition.id] = this.taskEdition
            } else {
                this.tasks.push(this.taskEdition)
            }
            this.draw()
            this.taskEdition = {}
            $().dialog({
                "close": "#task-dialog",
            })
            this.$refs.timePicker.onClosePicker()
            this.$refs.colorPicker.onClosePicker()
        },
        openTaskUpdateModal: function (id) {
            // console.log(id)
            this.taskEdition = this.tasks[id]
            $().dialog({
                "target":"#task-dialog",
                "size":"dialog-middle",
                "style":"bg-white radius-big shadow",
                "dismiss":false,
            })
        },
        deleteTask: function (id) {
            let newTaskArr = []
            let j = 0
            for (let i = 0; i < this.tasks.length; ++i) {
                let task = this.tasks[i]
                if (task.id !== id) {
                    task.id = j
                    ++j
                    newTaskArr.push(task)
                }
            }
            this.tasks = newTaskArr
            this.draw()
        },
        closeTaskEditModal: function () {
            this.taskEdition = {}
            $().dialog({
                "close": "#task-dialog",
            })
            this.$refs.timePicker.onClosePicker()
            this.$refs.colorPicker.onClosePicker()
        },
        move: function(id, op) {
            // console.log('move ' + id + ' ' + op)
            let maxId = this.tasks[this.tasks.length - 1].id
            if (op === UP) {
                if (id == 0 || id > maxId) return
                this.tasks[id].id -= 1
                this.tasks[id - 1].id += 1
            } else {
                if (id == maxId || id > maxId) return
                this.tasks[id].id += 1
                this.tasks[id + 1].id -= 1

            }
            this.draw()
        },
        formatDate: function(date) {
            return date.getFullYear()+'/'+(date.getMonth()+1)+'/'+date.getDate()
        },
        categoryFormatter: function(task) {
            return '任务名称：' + task.name + '\r\n开始时间：' + this.formatDate(new Date(task.startTime)) + '\r\n结束时间：' + this.formatDate(new Date(task.endTime));
        },
        draw: function () {
            this.validate()
            this.sortTasks()
            let formatDate = this.formatDate
            if (this.ganttChart !== null) {
                this.ganttChart.dispose()
            }
            if (this.tasks === null || this.tasks.length < 1) return
            let startTimes = []
            let duringTimes = []
            let categories = []
            let taskNames = []
            let colors = []
            let minTime = this.tasks[0].startTime
            for (let i = this.tasks.length - 1; i >= 0; --i) {
                startTimes.push(this.tasks[i].startTime)
                duringTimes.push(this.tasks[i].endTime - this.tasks[i].startTime)
                categories.push(this.categoryFormatter(this.tasks[i]))
                taskNames.push(this.tasks[i].name)
                colors.push(this.tasks[i].color)
                if (this.tasks[i].startTime < minTime) minTime = this.tasks[i].startTime
            }
            let option = {
                animation: false,
                backgroudColor: '#fff',
                color: colors,
                title: {
                    text: this.title,
                    textStyle: {
                        textSize: 12,
                        fontWeight: 'normal'
                    }
                },
                grid: {
                    containLabel: true,
                    left: 20
                },
                tooltip: {
                    trigger: 'axis',
                    enterable: true,
                    hideDelay: 10,
                    formatter: function (params) {
                        // console.log(params)
                        if (params === undefined || params.length < 3) return ''
                        let startDate = new Date(params[1].data)
                        let endDate = new Date(params[1].data + params[2].data)
                        return taskNames[params[0].dataIndex] + '<br/>开始时间：' + formatDate(startDate) + '<br/>结束时间：' + formatDate(endDate)
                    },
                },
                series: [
                    {
                        name: '任务开始时间',
                        type: 'bar',
                        stack: 'total',
                        itemStyle: {
                            borderColor: 'transparent',
                            color: 'transparent'
                        },
                        emphasis: {
                            itemStyle: {
                                borderColor: 'transparent',
                                color: 'transparent'
                            }
                        },
                        data: startTimes
                    },
                    {
                        name: '任务持续时间',
                        type: 'bar',
                        stack: 'total',
                        colorBy: 'data',
                        label: {
                            show: true,
                            position: 'right',
                            formatter: (param) => {
                                // console.log(param)
                                return taskNames[param.dataIndex]
                            }
                        },
                        data: duringTimes
                    },
                ],
                xAxis: {
                    type: 'value',
                    name: '时间',
                    min: minTime,
                    // maxInterval: ONE_DAY_MILLISECOND,
                    axisLabel: {
                        formatter: function (param) {
                            let date = new Date(param)
                            let dateLabel = formatDate(date)
                            return dateLabel
                        }
                    },
                    axisPointer: {
                        show: true,
                        label: {
                            backgroundColor: '#004f53',
                            margin: -20,
                            formatter: (params) => {
                                let date = new Date(Math.ceil(params.value))
                                return formatDate(date)
                                // return date.getFullYear()+'/'+(date.getMonth()+1)+'/'+date.getDate() + ' ' + date.getHours() + ':' + date.getMinutes() + ':' + date.getSeconds()
                            }
                        },
                        lineStyle: {
                            color: '#9fbfcd',
                            type: 'solid',
                            width: 1.5,
                        },
                    },
                },
                yAxis: {
                    type: 'category',
                    name: '任务列表',
                    splitLine: {
                        show: true,
                        lineStyle: 'dashed'
                    },
                    nameTextStyle: {
                        align: 'left'
                    },
                    data: categories
                }
            }
            //基于准备好的dom，初始化echarts实例
            this.ganttChart = echarts.init(document.getElementById('gantt-chart'))
            //使用刚指定的配置项和数据显示图表。
            this.ganttChart.setOption(option)
        },
        isColor: function (str) {
            if (str.length !== 7) {
                return false
            }
            return COLOR_PATTERN.test(str)
        },
        randomColor: function() {
            let color = '#'
            for (let i = 0; i < 6; ++i) {
                color += COLOR_CHARACTORS[Math.floor(Math.random() * 16)]
            }
            return color
        },
        validate: function () {
            if (this.errMsg.length > 0) alert(this.errMsg)
        },
        //将一个sheet转成最终的excel文件的blob对象，然后利用URL.createObjectURL下载
        sheet2blob: function (sheet, sheetName) {
            sheetName = sheetName || 'sheet1'
            let workbook = {
                SheetNames: [sheetName],
                Sheets: {}
            }
            workbook.Sheets[sheetName] = sheet
            // 生成excel的配置项
            let wopts = {
                bookType: 'xlsx', // 要生成的文件类型
                bookSST: false, // 是否生成Shared String Table，官方解释是，如果开启生成速度会下降，但在低版本IOS设备上有更好的兼容性
                type: 'binary'
            }
            let wbout = XLSX.write(workbook, wopts)
            let blob = new Blob([s2ab(wbout)], {type:"application/octet-stream"})
            // 字符串转ArrayBuffer
            function s2ab(s) {
                let buf = new ArrayBuffer(s.length)
                let view = new Uint8Array(buf)
                for (let i=0; i!=s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF
                return buf
            }
            return blob
        },
        /**
        * 通用的打开下载对话框方法，没有测试过具体兼容性
        * @param url 下载地址，也可以是一个blob对象，必选
        * @param saveName 保存文件名，可选
        */
        openDownloadDialog: function(url, saveName) {
            if (typeof url == 'object' && url instanceof Blob){
                url = URL.createObjectURL(url) // 创建blob地址
            }
            let aLink = document.createElement('a')
            aLink.href = url
            aLink.download = saveName || '' // HTML5新增的属性，指定保存文件名，可以不要后缀，注意，file:///模式下不会生效
            let event
            if (window.MouseEvent) event = new MouseEvent('click')
            else {
                event = document.createEvent('MouseEvents')
                event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null)
            }
            aLink.dispatchEvent(event)
        },
        /**
         * 将任务列表转化为sheet然后导出Excel下载
         * XLSX资料：https://www.cnblogs.com/ajaemp/p/12880847.html
         */
        exportTasks: function() {
            if (this.tasks.length < 1) {
                alert('没有任务数据，不能导出文件')
                return
            }
            let sheet = {} // 将要生成的sheet
            //String.fromCharCode(65)为A，计算要多少列就看取task的多少个字段，!ref是从A1至数据最右下角的格子
            sheet['!ref'] = 'A1:' + String.fromCharCode(65 + 4) + (this.tasks.length + 1)
            //写入表头
            sheet[String.fromCharCode(65 + 0) + 1] = {v: '任务名称'}
            sheet[String.fromCharCode(65 + 1) + 1] = {v: '开始时间'}
            sheet[String.fromCharCode(65 + 2) + 1] = {v: '结束时间'}
            sheet[String.fromCharCode(65 + 3) + 1] = {v: '颜色'}
            //写入任务数据，每条任务占一行
            this.tasks.forEach(function(task, i) {
                sheet[String.fromCharCode(65 + 0) + (i+2)] = {v: task.name}
                sheet[String.fromCharCode(65 + 1) + (i+2)] = {v: task.startTimeText}
                sheet[String.fromCharCode(65 + 2) + (i+2)] = {v: task.endTimeText}
                sheet[String.fromCharCode(65 + 3) + (i+2)] = {v: task.color}
            })
            // console.log(sheet)
            let blob = this.sheet2blob(sheet)
            this.openDownloadDialog(blob, 'output.xlsx')
        },
        //解析Excel文件，注意，只读取第一个sheet，多余的sheet忽略
        importDataFromWorkbook: function (workbook) {
            // console.log(workbook)
            let sheetName = workbook.SheetNames[0] //获取第一个工作表名称
            let worksheet = workbook.Sheets[sheetName] // 只能通过工作表名称来获取指定工作表
            if (worksheet === null) {
                alert('文件没有工作表')
                return
            }
            let ref = worksheet['!ref']
            if (ref === null) {
                alert('文件工作表不合法，缺少工作表范围')
                return
            }
            let refSplits = ref.split(':')
            if (refSplits.length !== 2) {
                alert('文件工作表不合法，工作表范围记录不合法')
                return
            }
            let colLen = refSplits[1].charCodeAt(0) - refSplits[0].charCodeAt(0) + 1
            // console.log('colLen: ' + colLen)
            if (Number.isNaN(colLen) || colLen < 4) {
                alert('文件工作表列不符合要求')
                return
            }
            let dataLen = parseInt(refSplits[1].substring(1)) - parseInt(refSplits[0].substring(1))
            // console.log('dataLen: ' + dataLen)
            if (Number.isNaN(dataLen) || dataLen < 1) {
                alert('文件工作表为空，没有数据导入')
                return
            }
            let nameCell = worksheet[String.fromCharCode(65 + 0) + 1]
            let startTimeCell = worksheet[String.fromCharCode(65 + 1) + 1]
            let endTimeCell = worksheet[String.fromCharCode(65 + 2) + 1]
            let colorCell = worksheet[String.fromCharCode(65 + 3) + 1]
            if (undefined === nameCell || undefined === startTimeCell || undefined === endTimeCell || undefined === colorCell) {
                alert('表头残缺，导入终止')
                return
            }
            if (nameCell.w !== '任务名称' || startTimeCell.w !== '开始时间' || endTimeCell.w !== '结束时间' || colorCell.w !== '颜色') {
                alert('表头没有按照“任务名称”、“开始时间”、“结束时间”、“颜色”的顺序一一对应，导入终止')
                return
            }
            let tasks = []
            for(let i = 0; i < dataLen; ++i) {
                // v是读取单元格的原始值
                // console.log(key, key[0] === '!' ? worksheet[key] : worksheet[key].v)
                nameCell = worksheet[String.fromCharCode(65 + 0) + (i+2)]
                startTimeCell = worksheet[String.fromCharCode(65 + 1) + (i+2)]
                endTimeCell = worksheet[String.fromCharCode(65 + 2) + (i+2)]
                colorCell = worksheet[String.fromCharCode(65 + 3) + (i+2)]
                if (undefined === nameCell || undefined === startTimeCell || undefined === endTimeCell || undefined === colorCell) {
                    alert('第'+(i+2)+'行数据残缺，导入终止')
                    return
                }
                let task = {
                    name: nameCell.w,
                    startTimeText: this.formatDate(new Date(startTimeCell.w)),
                    endTimeText: this.formatDate(new Date(endTimeCell.w)),
                    color: colorCell.w,
                }
                tasks.push(task)
            }
            for (let i = 0; i < tasks.length; ++i) {
                tasks[i].id = i
                // console.log(tasks[i])
                if (!this.validateTask(tasks[i])) {
                    alert(this.errMsg + tasks[i].name)
                    return
                }
            }
            this.tasks = tasks
            this.draw()
        },
        //读取本地excel文件
        readWorkbookFromLocalFile: function (file, callback) {
            let reader = new FileReader()
            reader.onload = function(e) {
                let data = e.target.result
                let workbook = XLSX.read(data, {type: 'binary'})
                if(callback) callback(workbook)
            };
            reader.readAsBinaryString(file)
        },
        importTasks: function() {
            let importDataFromWorkbook = this.importDataFromWorkbook
            // console.log('confirm to import...')
            let importCall = async () => {
                //打开文件选择框资料：https://blog.csdn.net/XianZhe_/article/details/129205771
                let [fileHandler] = await window.showOpenFilePicker({
                    types: [
                        {
                            description: 'Excel文件',
                            accept: {
                                'xlsx/*': ['.xlsx']
                            }
                        }
                    ]
                })
                let file = await fileHandler.getFile()
                // console.log(file)
                this.readWorkbookFromLocalFile(file, importDataFromWorkbook)
            }
            importCall()
        },
        openImportConfimation: function() {
            let importTasks = this.importTasks
            $().dialog({
                "target":"#import-confirm",
                "onconfirm": function() {
                    importTasks()
                },
            })
        },
        updateTaskEdition: function(taskEdition) {
            let newEdition = {}
            for (let key in taskEdition) {
                newEdition[key] = taskEdition[key]
            }
            this.taskEdition = newEdition
        },
        pickStartDate: function(params) {
            // console.log(params)
            let updateTaskEdition = this.updateTaskEdition
            let taskEdition = this.taskEdition
            let param = {
                time: new Date(this.taskEdition.startTimeText).getTime(),
                top: params.clientY,
                left: params.clientX,
                callback: function(timeText) {
                    taskEdition.startTimeText = timeText
                    updateTaskEdition(taskEdition)
                },
            }
            this.$refs.timePicker.openDatePicker(param)
        },
        pickEndDate: function(params) {
            // console.log(params)
            let updateTaskEdition = this.updateTaskEdition
            let taskEdition = this.taskEdition
            let param = {
                time: new Date(this.taskEdition.endTimeText).getTime(),
                top: params.clientY,
                left: params.clientX,
                callback: function(timeText) {
                    taskEdition.endTimeText = timeText
                    updateTaskEdition(taskEdition)
                },
            }
            this.$refs.timePicker.openDatePicker(param)
        },
        pickColor: function(params) {
            // console.log(params)
            let updateTaskEdition = this.updateTaskEdition
            let taskEdition = this.taskEdition
            let param = {
                color: taskEdition.color,
                top: params.clientY,
                left: params.clientX,
                callback: function(color) {
                    taskEdition.color = color
                    updateTaskEdition(taskEdition)
                },
            }
            this.$refs.colorPicker.openColorPicker(param)
        },
    },
})
